package com.acme.javaschool.intense.bankapp.network;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.StringTokenizer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import com.acme.javaschool.intense.bankapp.domain.Bank;
import com.acme.javaschool.intense.bankapp.domain.Client;
import com.acme.javaschool.intense.bankapp.exceptions.ClientExistsException;
import com.acme.javaschool.intense.bankapp.service.AccountService;
import com.acme.javaschool.intense.bankapp.service.BankIOService;
import com.acme.javaschool.intense.bankapp.service.ClientService;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

public class BankServer implements Runnable {

    public static final int COMMAND_QUEUE_CAPACITY = 100500*4;
    ObjectOutputStream utilOut;

    //	ServerSocket providerSocket;
    //	Socket connection = null;
    //	ObjectOutputStream out;
    //	ObjectInputStream in;
    String message;

    Bank bank;
    ClientService clientService;
    AccountService accountService;
    BankIOService bIOService;

    BlockingQueue<String> commandsQueue = new ArrayBlockingQueue<>(COMMAND_QUEUE_CAPACITY);

    public BankServer(Bank bank, ClientService clientService, AccountService accountService, BankIOService bIOService) {
        this.bank = bank;
        this.clientService = clientService;
        this.accountService = accountService;
        this.bIOService = bIOService;
    }

    public BankServer() {
        this.bank = new Bank();
        this.clientService = new ClientService(bank);
        this.accountService = new AccountService(bank);
        this.bIOService = new BankIOService(bank);

        new Thread("CommandsProcessor") {
            @Override
            public void run() {
                int count = 0;
                while (true) {
                    String command = null;
                    try {
                        command = commandsQueue.take();
                        System.out.println(count++);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    message = processCommand(command);
//                    System.out.println("[Server] " +"client>" + command);
//                    sendMessage(message, utilOut);
                    if ("bye".equals(message)) {
                        sendMessage("bye", utilOut);
                    }
                }
            }
        }.start();
    }

    /**
     * Listens for incoming connections.
     * Initializes input/output streams (Object...Stream).
     * Handles communication.
     */
    public void run() {
//		try (ServerSocket providerSocket = new ServerSocket(2004, 10)) {
        try {
            ServerSocket providerSocket = new ServerSocket(2004, 10);
            while (true) {
                // 1. creating a server socket
                // 2. Wait for connection
                System.out.println("[Server] " +"Waiting for connection");
                Socket connection = providerSocket.accept();
                System.out.println("[Server] " +"Connection received from "
                        + connection.getInetAddress().getHostName());

                // 3. get Input and Output streams
                try (   ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
                        ObjectInputStream  in = new ObjectInputStream(connection.getInputStream())  ) {
                    utilOut = out;
                    out.flush();
                    sendMessage("Connection successful", out);

                    communicate(in, out);

                } catch (IOException e) {
                    if (e.getClass() == SocketException.class) {
                        System.out.println("--- [Server] Socket appears to be closed...");
                    } else {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Handles communication.
     * @param out ObjectOutputStream
     * @param in ObjectInputStream
     * @throws java.io.IOException
     */
    void communicate(ObjectInputStream in, ObjectOutputStream out)
            throws IOException {
        String command = "";
        // 4. The two parts communicate via the input and output streams
        do {
            try {
                command = (String) in.readObject();
                commandsQueue.add(command);
            } catch (ClassNotFoundException classnot) {
                System.err.println("Data received in unknown format");
            } catch (EOFException eof) {
                System.out.println("Disconnected.");
                message = "bye";
            }
        } while (!"bye".equals(message));
    }


    /**
     * Parses command from client and executes it.
     * @return
     */
    private String processCommand(String command) {
        if (command == null) {
            return "Command should not be null!";
        } else if ("bye".equals(command)) {
            return "bye";
        } else if ("printAllClients".equals(command)) {
            return "\n" + BankIOService.getAllAccountsString(bank);
        } else if (command.indexOf("addClient") == 0) {
            Client client = bIOService.createClientFromStringTokenizer(new StringTokenizer(command.substring(9)), ";");
            try {
                bank.addClient(client);
//                System.out.println("COME OOON!");
                return "\nClient " + client + " has been added.";
            } catch (ClientExistsException e) {
                return "\nClient " + client + " has NOT been added - already exists.";
            }
        } else {
            return "Command \"" + command + "\" unknown.";
        }
    }

    void sendMessage(final Object msg, ObjectOutputStream out) {
        try {
//            System.out.println("now now now now nown ow now " + msg + " kjdslhfwehlfhdsalhflwaeiurh");
            out.writeObject(msg);
//            System.out.println("now now now now nown ow now 123123123");
            out.flush();
//            System.out.println("[Server] " +"server>" + msg);
        } catch (IOException ioException) {
//            System.out.println("--- [Server] Output error (connection may be lost or terminated).");
        }
    }

}
